package com.neborosoft.FlyingDog.logic.database;

import com.neborosoft.FlyingDog.logic.database.content.identified.*;
import com.neborosoft.FlyingDog.logic.database.providers.CountryGenreDefinition;
import com.neborosoft.FlyingDog.logic.search.InternetSearchEngine;
import com.neborosoft.FlyingDog.logic.search.navigation.*;
import com.neborosoft.lib.API_8.Collections;
import com.neborosoft.lib.collections.Containers;
import com.neborosoft.lib.collections.list.NavigationList;
import com.neborosoft.lib.handlers.ErrorListener;
import com.neborosoft.lib.threading.OnHostThreadExecutor;
import com.neborosoft.lib.utilities.algorithms.ObjectCoefficientProvider;
import com.neborosoft.lib.utilities.algorithms.ObjectProbabilityRangeMap;
import com.neborosoft.lib.utilities.algorithms.Search;

import java.util.*;
import java.util.concurrent.Executor;

/**
 * Created with IntelliJ IDEA.
 * User: CM
 * Date: 02.01.13
 * Time: 3:42
 * To change this template use File | Settings | File Templates.
 */
public class AudioDataManager {
    private static final int SIMILAR_TRACKS_PER_PAGE_COUNT = 30;
    private static final float SONGS_YOU_MAY_LIKE_COUNT_COEFFICIENT = 2.0f;
    private static final int GENRES_OF_COUNTRY_MAX_COUNT = 150;

    private static AudioDataManager instance;

    public static AudioDataManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException("call init before getInstance");
        }

        return instance;
    }

    private LocalAudioDataBase localAudioDataBase;
    private InternetSearchEngine internetSearchEngine = new InternetSearchEngine();
    private OnHostThreadExecutor onHostThreadExecutor;
    private Executor asyncTasksExecutor;
    private Executor songsYouMayLikeExecutor;

    private NavigationList<Object> activeNavigationList;

    private ErrorListener errorListener = new ErrorListener() {
        @Override
        public void onError(Throwable e) {

        }
    };

    public static void init(OnHostThreadExecutor onHostThreadExecutor,
                            Executor asyncTasksExecutor,
                            Executor songsYouMayLikeExecutor,
                            LocalAudioDataBase localAudioDataBase){
        if (onHostThreadExecutor == null || asyncTasksExecutor == null) {
            throw new NullPointerException();
        }

        if (instance != null) {
            throw new IllegalStateException("twice init call");
        }

        instance = new AudioDataManager();
        instance.onHostThreadExecutor = onHostThreadExecutor;
        instance.asyncTasksExecutor = asyncTasksExecutor;
        instance.songsYouMayLikeExecutor = songsYouMayLikeExecutor;
        instance.localAudioDataBase = localAudioDataBase;
    }

    private AudioDataManager() {

    }

    public List<Audio> getSongs(){
        return localAudioDataBase.getSongs();
    }

    public NavigationList<Audio> getSongs(String query){
        List<Audio> localAudios = localAudioDataBase.getSongs();
        SongsNavigationList.InitParams initParams = getPageNavigationListInitialParams(localAudios, query);

        SongsNavigationList songs = new SongsNavigationList(initParams,
                asyncTasksExecutor, onHostThreadExecutor);
        songs.setErrorListener(errorListener);
        return songs;
    }

    public List<PlayList> getPlayLists(){
        List<PlayList> result = new ArrayList<PlayList>();

        List<PlayList> specialPlayLists = PlayLists.getSpecialPlayLists();
        List<PlayList> localPlayLists = localAudioDataBase.getPlayLists();

        result.addAll(specialPlayLists);
        result.addAll(localPlayLists);

        return result;
    }

    public List<PlayList> getPlayLists(String query, int maxCount){
        List<PlayList> playLists = getPlayLists();
        return Search.filter(playLists, query, maxCount);
    }

    private <T> PageNavigationList.InitParams<T> getPageNavigationListInitialParams(
            List<T> localElements,
            String query)
    {
        localElements = Search.filter(localElements, query);

        PageNavigationList.InitParams<T> initParams = new PageNavigationList.InitParams();
        initParams.query = query;
        initParams.internetSearchEngine = internetSearchEngine;
        initParams.initialElements = localElements;

        return initParams;
    }

    public List<Album> getAlbums(){
        return localAudioDataBase.getAlbums();
    }

    public NavigationList<Album> getAlbums(String query){
        List<Album> localAlbums = getAlbums();

        AlbumsNavigationList.InitParams initParams = getPageNavigationListInitialParams(localAlbums, query);

        AlbumsNavigationList albums = new AlbumsNavigationList(initParams,
                asyncTasksExecutor, onHostThreadExecutor);
        albums.setErrorListener(errorListener);
        return albums;
    }

    public List<Audio> getTracksOfAlbum(Album album){
        if(album.isLocal()){
            return localAudioDataBase.getSongsOfAlbum(album);
        } else {
            return internetSearchEngine.getSongsOfAlbum(album);
        }
    }

    public NavigationList<Audio> getTracksOfArtist(Artist artist){
        if(artist.isLocal()){
            List<Audio> local = localAudioDataBase.getSongsOfArtist(artist);
            return NavigationList.decorate(local);
        } else {
            List<Audio> initialElements = Collections.emptyList();
            String artistName = artist.getName();
            if(artistName == null){
                return NavigationList.emptyList();
            }

            ArtistSongsNavigationList.InitParams params =
                    getPageNavigationListInitialParams(initialElements,artistName);
            return new ArtistSongsNavigationList(params, asyncTasksExecutor, onHostThreadExecutor);
        }
    }

    public NavigationList<Audio> getTracksByTag(String tag){
        TagSongsNavigationList.InitParams params =
                getPageNavigationListInitialParams(Collections.emptyList(), tag);
        return new TagSongsNavigationList(params, asyncTasksExecutor, onHostThreadExecutor);
    }

    public NavigationList<Artist> getArtistsByTag(String genre){
        GenreArtistsNavigationList.InitParams params =
                getPageNavigationListInitialParams(Collections.emptyList(), genre);
        return new GenreArtistsNavigationList(params, asyncTasksExecutor, onHostThreadExecutor);
    }

    public NavigationList<Album> getAlbumsOfArtist(Artist artist){
        if(artist.isLocal()){
            List<Album> local = localAudioDataBase.getAlbumsOfArtist(artist);
            return NavigationList.decorate(local);
        } else {
            List<Audio> initialElements = Collections.emptyList();
            String artistName = artist.getName();
            if(artistName == null){
                return NavigationList.emptyList();
            }

            ArtistSongsNavigationList.InitParams params =
                    getPageNavigationListInitialParams(initialElements,artistName);
            return new ArtistAlbumsNavigationList(params, asyncTasksExecutor, onHostThreadExecutor);
        }
    }

    private List<Audio> getSongsOfSpecialPlayList(PlayList playList){
        int id = (Integer)playList.getId();
        switch (id) {
            case PlayLists.SONGS_YOU_MAY_LIKE_PLAYLIST_ID:
                return getSongsYouMayLike();
            default:
                return Collections.emptyList();
        }
    }

    public List<Audio> getTracksOfPlayList(PlayList playList){
        if(playList.isLocal()){
            return localAudioDataBase.getSongsOfPlayList(playList);
        } else {
            return getSongsOfSpecialPlayList(playList);
        }
    }

    public List<Artist> getArtists(){
        return localAudioDataBase.getArtists();
    }

    public NavigationList<Artist> getArtists(String query){
        List<Artist> localArtists = getArtists();

        ArtistsNavigationList.InitParams initParams = getPageNavigationListInitialParams(localArtists, query);

        ArtistsNavigationList artists = new ArtistsNavigationList(initParams,
                asyncTasksExecutor, onHostThreadExecutor);
        artists.setErrorListener(errorListener);
        return artists;
    }

    public boolean artistHasTracks(Artist artist){
        if(!artist.isLocal()){
            return true;
        } else {
            return localAudioDataBase.artistHasTracks(artist);
        }
    }

    public boolean artistHasAlbums(Artist artist){
        if(!artist.isLocal()){
            // not implemented yet, some artists do not have albums
            return true;
        } else {
            return localAudioDataBase.artistHasAlbums(artist);
        }
    }

    public Iterable<String> getSuggestedAudioUrls(Audio audio){
        return internetSearchEngine.getAudioUrls(audio);
    }

    public NavigationList<Audio> getSongsYouMayLike(){
        SongsYouMayLikeNavigationList.Params params = new SongsYouMayLikeNavigationList.Params();
        params.userPlaylist = getSongs();
        params.songsCount = (int)(SONGS_YOU_MAY_LIKE_COUNT_COEFFICIENT * params.userPlaylist.size());
        params.songsCountPerPage = SIMILAR_TRACKS_PER_PAGE_COUNT;
        params.internetSearchEngine = internetSearchEngine;
        params.asyncTaskExecutor = songsYouMayLikeExecutor;
        params.onHostThreadExecutor = onHostThreadExecutor;

        return new SongsYouMayLikeNavigationList(params);
    }

    public List<CountryGenreDefinition> getGenresOfCountry(String countryName){
        List<String> tags = internetSearchEngine.
                searchTagsHandleErrors(countryName, GENRES_OF_COUNTRY_MAX_COUNT, 0).elements;

        List<CountryGenreDefinition> result = new ArrayList<CountryGenreDefinition>(tags.size());

        for(String tag : tags){
            int genreBeginIndex = tag.indexOf(countryName) + countryName.length() + 1;
            if(genreBeginIndex >= 0 && genreBeginIndex < tag.length() - 1){
                String genreCandidate = tag.substring(genreBeginIndex);

                if(Genres.genreExists(genreCandidate)){
                    CountryGenreDefinition countryGenreDefinition = new CountryGenreDefinition();
                    countryGenreDefinition.genreName = genreCandidate;
                    countryGenreDefinition.tagName = tag;
                    result.add(countryGenreDefinition);
                }

            }
        }

        return result;
    }

    MultiTagNavigationList.Params getMultiTagNavigationListParams(){
        MultiTagNavigationList.Params params = new MultiTagNavigationList.Params();
        params.internetSearchEngine = internetSearchEngine;
        params.dataLoadingExecutor = asyncTasksExecutor;
        params.onHostThreadExecutor = onHostThreadExecutor;
        return params;
    }

    MultiTagNavigationList.Params getCountryNavigationListParams(String countryName){
        MultiTagNavigationList.Params params = getMultiTagNavigationListParams();
        params.tags = Countries.getCountryTags(countryName);
        return params;
    }

    MultiTagNavigationList.Params getMoodNavigationListParams(String moodName){
        MultiTagNavigationList.Params params = getMultiTagNavigationListParams();
        params.tags = Mood.getMoodTags(moodName);
        return params;
    }

    public NavigationList<Audio> getSongsByCountry(String countryName){
        MultiTagNavigationList.Params params = getCountryNavigationListParams(countryName);
        return new MultiTagsSongsNavigationList(params);
    }

    public NavigationList<Artist> getArtistsByCountry(String countryName){
        MultiTagNavigationList.Params params = getCountryNavigationListParams(countryName);
        return new MultiTagsArtistNavigationList(params);
    }

    public NavigationList<Audio> getSongsByMood(String moodName){
        MultiTagNavigationList.Params params = getMoodNavigationListParams(moodName);
        return new MultiTagsSongsNavigationList(params);
    }

    public NavigationList<Artist> getArtistsByMood(String moodName){
        MultiTagNavigationList.Params params = getMoodNavigationListParams(moodName);
        return new MultiTagsArtistNavigationList(params);
    }

    public void startAlbumArtsUpdating(LocalAudioDataBase.OnArtsUpdatingFinished onArtsUpdatingFinished){
        localAudioDataBase.startAlbumArtsUpdating(onArtsUpdatingFinished);
    }
}
